/*
    Calendar.cpp

    Date:
        05-09-10

    Descripton:
        A container for calendar events, including support
        to read data from a file

    Authors:
        Nathaniel Brown
        Subodh Prabhu
        Jesus Suarez
*/


/****************************************************************************/
/*                              I N C L U D E S                             */
/****************************************************************************/

#include "GlutHeaders.h"
#include "Calendar.h"
#include "DrawGraphics.h"


/****************************************************************************/
/*                      U T I L I Y   F U N C T I O N S                     */
/****************************************************************************/

bool byStartTime(Item a, Item b) {
  return (a.getStartTime() < b.getStartTime());
}


/****************************************************************************/
/*           C A L E N D A R   :   M E M B E R   F U N C T I O N S          */
/****************************************************************************/

Calendar::Calendar() {    
}

void
Calendar::getItemsForPeriod(list<Item>* result, time_t start, time_t end) {
  list<Item>::iterator it;
  for(it = items.begin(); it != items.end(); ++it) {
    double d_start_start = difftime(((Item)*it).getStartTime(), start);
    double d_start_end = difftime(((Item)*it).getStartTime(), end);
    double d_end_start = difftime(((Item)*it).getEndTime(), start);
    double d_end_end = difftime(((Item)*it).getEndTime(), end);
    Item to_insert = (Item) *it;

    if(d_start_start >= 0 && d_end_end <= 0) {
      result->push_back(to_insert);
    }
    else if(d_start_start < 0 && d_end_end <= 0 && d_end_start >= 0) {
      to_insert.setStartDate(start);
      result->push_back(to_insert);
    }
    else if(d_start_start >= 0 && d_end_end > 0 && d_start_end <= 0) {
      to_insert.setEndDate(end);
      result->push_back(to_insert);
    }
    else if(d_start_start < 0 && d_end_end > 0) {
      to_insert.setStartDate(start);
      to_insert.setEndDate(end);
      result->push_back(to_insert);
    }
  }
  result->sort(byStartTime);
}

int
Calendar::getNumberOfItems() {
  return items.size();
}

/****************************************************************************/
/*          c r e a t e F r o m F i l e   F u n c t i o n a l i t y         */
/****************************************************************************/

/* --------------------- satic data and local defines --------------------- */

Calendar* Calendar::calFromFile = NULL;
typedef stack<TUserData> TUserDataStack;
TUserDataStack dataStack;
Item * currentItem = NULL;
bool weCare = false;
int currentAttribute = 0;

#define STARTDATE   1
#define ENDDATE     2
#define TITLE       3
#define DESCRIPTION 4
#define PRIORITY    5

/* ------------------------ file reading functions ------------------------ */

Calendar*
Calendar::beingCreated() {
    return calFromFile;
}

void
Calendar::PropHandler(void *userData, const CARD_Char *propName, const CARD_Char **params) {
  if (strcmp(propName, "BEGIN") == 0) {
    // begin: vcard/vcal/whatever
    dataStack.push(TUserData());
    dataStack.top().inBegin = true;
    return;
  }

  if (dataStack.empty()) {
    cout << "ERROR:Property encountered with no BEGIN:" << endl;
    return;
  }

  if (strcmp(propName, "END") == 0) {
    // end: vcard/vcal/whatever
    // check data
    if(!currentItem)
      return;

    if(!currentItem->isValid()) {
      cout << "ERROR: did not insert item into Calendar vector \"items\" because it is invalid." << endl;
      return; // don't insert an invalid item...
    }

    //insert into items vector
    Calendar::beingCreated()->items.push_back(*currentItem);
    currentItem = NULL;
    weCare = false;
  }

  else if (strcmp(propName, "DTSTART") == 0)
    currentAttribute = STARTDATE;

  else if (strcmp(propName, "TRIGGER") == 0)
    currentAttribute = STARTDATE;

  else if (strcmp(propName, "DTEND") == 0)
    currentAttribute = ENDDATE;
  
  else if (strcmp(propName, "SUMMARY") == 0)
    currentAttribute = TITLE;
  
  else if (strcmp(propName, "DESCRIPTION") == 0)
    currentAttribute = DESCRIPTION;
    
  else if (strcmp(propName, "PRIORITY") == 0)
    currentAttribute = PRIORITY;
  
  else
    currentAttribute = 0;
  
}

void
Calendar::DataHandler(void *userData, const CARD_Char *data, int len) {
  if (dataStack.empty()) {
    cout << "ERROR: data encountered with no BEGIN:" << endl;
    return;
  }

  if (dataStack.top().inBegin) {              // new item (potentially)
    char *tData = (char*) malloc(sizeof(char) * (len+1));
    strncpy(tData, data, len);
    tData[len] = '\0';
    //cout << ":(" << len << "):" << data << " | " << tData << endl;

    if(strcmp(tData, "VEVENT") == 0) {
      currentItem = new Item();
      currentItem->setType(VEVENT); 
      weCare = true;
    }

    else if(strcmp(tData, "VTODO") == 0) {
      currentItem = new Item();
      currentItem->setType(VTODO); 
      weCare = true;
    }

    else
      weCare = false;

    dataStack.top().inBegin = false;
  }
  else if (dataStack.top().inEnd && weCare && currentItem) {   //reached end of item record
    return;
    /*
    // check data
    if(!currentItem->isValid()) {
      cout << "ERROR: did not insert item into Calendar vector \"items\" because it is invalid." << endl;
      return; // don't insert an invalid item...
    }

    //insert into items vector
    Calendar::beingCreated()->items.insert(*currentItem);
    //~currentItem;
    currentItem = NULL;
    weCare = false;
    */
  }

	else if (len !=0 && weCare && currentItem && currentAttribute) {
    time_t date;
    struct tm * theTime;
    string value (data);
    
    if(currentAttribute == STARTDATE || currentAttribute == ENDDATE) {
      // convert date string to time_t
      time(&date);
      theTime = localtime(&date);
      theTime->tm_year = atoi((new string (value, 0, 4))->c_str()) - 1900;
      theTime->tm_mon = atoi((new string (value, 4, 2))->c_str()) - 1;
      theTime->tm_mday = atoi((new string (value, 6, 2))->c_str());
      theTime->tm_hour = atoi((new string (value, 9, 2))->c_str());
      theTime->tm_min = atoi((new string (value, 11, 2))->c_str());
      date = mktime(theTime);
    }

    if(currentAttribute == STARTDATE)
      currentItem->setStartDate(date);

    else if(currentAttribute == ENDDATE)
      currentItem->setEndDate(date);

    else if(currentAttribute == TITLE)
      currentItem->setTitle(value);
    
    else if(currentAttribute == DESCRIPTION)
      currentItem->setDescription(value);
      
    else if(currentAttribute == PRIORITY)
      currentItem->setLevel(atoi((new string (value, 0, 1))->c_str()));
      //currentItem->setLevel(3);
  }
}

bool
Calendar::createFromFile(Calendar* curCal, string file) {   
  // open file
  ifstream is(file.c_str(), ios::binary);
  if (! is) {
    cout << "ERROR: Unable to open file" << file << endl;
    return false;
  }

  calFromFile = curCal;

  // allocate parser
  CARD_Parser vp = CARD_ParserCreate(NULL);

	// initialise
	CARD_SetPropHandler(vp, PropHandler);
	CARD_SetDataHandler(vp, DataHandler);

  // parse
	bool parseErr = false;
	char buf[512];
	is.read(buf, sizeof(buf));

  while (is.gcount() > 0) {
    int len = is.gcount();
    int rc = CARD_Parse(vp, buf, len, false);
    if (rc == 0) {
      parseErr = true;
      cout << "ERROR: Error parsing in createFromFile()" << endl;
      break;
    }

    is.read(buf, sizeof(buf));
  }

	// finalise parsing
	if (! parseErr) {
    CARD_Parse(vp, NULL, 0, true);
  }

  // free parser
  CARD_ParserFree(vp);

  calFromFile = NULL;

  // sort list of items
  if(Calendar::beingCreated() && !Calendar::beingCreated()->items.empty())
    Calendar::beingCreated()->items.sort(byStartTime);

  return true;
}
